#include "main.h"
#include "DHTesp.h"
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "assitant.h"
#include "wifis.h"
#include "mqtts.h"
#include "LittleFS.h"

#define fs LittleFS
const int __firmware_version = 221129;

float report_time = 60;
void (*_rep_data_hook)(String &data_str) = NULL;

WiFiEventHandler STAConnected, STADisConnected;

void e_wifi_connect(const WiFiEventStationModeConnected &e);
void e_wifi_disconnect(const WiFiEventStationModeDisconnected &e);
void *__lily_out_adapter(void *arg, char *msg, int len);
Pipe __std_out_pipe_handle = NULL, __std_in_pipe_handle = NULL;
unsigned int __get_ms()
{
  return millis();
}
void setup()
{
  // GIPIO setup
  digitalWrite(LED_BUILTIN, 1);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  STAConnected = WiFi.onStationModeConnected(e_wifi_connect);
  STADisConnected = WiFi.onStationModeDisconnected(e_wifi_disconnect);
  __std_out_pipe_handle = new_output_device("std", NULL, __lily_out_adapter);
  __std_in_pipe_handle = new_input_device("std");
  Pipe __lily_in_pipe_handle = lily_pipe_io_setup(__std_out_pipe_handle);
  pipe_connect_to(__std_in_pipe_handle, __lily_in_pipe_handle);

  lily_millis = __get_ms;
  lily_init();

  if (!fs.begin())
  {
    xouts("format begin");
    bool ok = fs.format();
    xouts("format done");
    if (!ok)
      xouts("bad news");
    var f = fs.open(config_file, "w");
    // f.write("sys 1\n");
    f.write("sensor dht11:D2\n");
    f.write("slp 60\n");
    f.close();
  }
  if (!fs.exists(config_file))
  {
    var f = fs.open(config_file, "w");
    f.write("sensor dht11:D2\n");
    f.write("slp 60\n");
    f.close();
  }
  // public cmds here
  mqtt_setup(on_message);
  extern int __serial_echo;
  public_a_var_ref("_ate", cast(&__serial_echo, void *), 'd');
  new_cmd("led", cmd_led);
  new_cmd("rep", cmd_rep);
  new_cmd("wifi", cmd_wifi);
  new_cmd("ping", cmd_test);
  new_cmd("beep", cmd_beep);
  new_cmd("slp", cmd_sleep);

#include "main_ini.h.init"

  config_setup();
  new_cmd("restart", cmd_restart);
  if (WiFi.status() != WL_CONNECTED)
  {
    int ok = setup_wifi(); // block here
    if (ok == 0)
    {
      public_a_timer(shine_blink, Hz(1));
    }
    else
    {
      do_cmd("sap start 192.168.2.1 esp8266");
      if (cmd_return_code < 0)
        public_a_timerArg(cast(setup_wifi, TasksArg_def), Second(60), (void *)1);
      public_a_timer(shine_blink, Hz(2));
    }
  }
  else
  {
    public_a_timer(shine_blink, Hz(1));
  }
  public_a_timer(serial_query, Hz(27));
  if (!had_timer(rep))
    public_a_timer(rep, Second(report_time));
  resume_schedule();
}

static bool __wifi_connected = false;
void e_wifi_connect(const WiFiEventStationModeConnected &e)
{
  __wifi_connected = true;
  change_timer_per(shine_blink, Hz(1));
  addTask_(event_wifi_connected);
  remove_timerArg(cast(setup_wifi, TasksArg_def), (void *)1);
}

void e_wifi_disconnect(const WiFiEventStationModeDisconnected &e)
{
  if (!__wifi_connected)
  {
    return;
  }
  __wifi_connected = false;
  change_timer_per(shine_blink, Hz(2));
  if (e.reason == WIFI_DISCONNECT_REASON_AUTH_FAIL)
  {
    WiFi.setAutoReconnect(false);
    return;
  }
}
void loop()
{
  if (front == rear)
    return;
  if (tasks_[front]() == 1)
  {
    tasks_[rear++] = tasks_[front];
    if (rear >= Tasks_LEN)
      rear = 0;
  }
  if (++front == Tasks_LEN)
    front = 0;
}

/*
perform file check
dispatch mqtt work loop

*/
int event_wifi_connected()
{
  WiFi.setAutoReconnect(true);
  var _in_ticking = in_ticking;
  if (in_ticking)
    stop_schedule();
  // var wifi_configs = cmd_search_file("wifi");
  var line_at = 0;
  var wifi_configs = cmd_search_file([](String &row) -> bool
                                     {
                                       if (!row.startsWith("wifi"))
                                         return false;
                                       var para_seg_at = row.indexOf(' ');
                                       if (para_seg_at <= 0)
                                         return false;
                                       int i = para_seg_at + 1;
                                       var s = row.c_str();
                                       for (; s[i] != '\0'; i++)
                                       {
                                         if (s[i] != ' ')
                                           break;
                                       }
                                       if (s[i] == '\0' || s[i] == '-')
                                         return false;
                                       return true; },
                                     line_at);
  bool need_dump = true;
  if (!wifi_configs.isEmpty())
  {
    var at = wifi_configs.indexOf(' ');
    if (at)
    {
      var s = wifi_configs.substring(at + 1);
      at = s.indexOf(' ');
      if (at)
      {
        s = s.substring(0, at);
        if (s == ssid)
        {
          need_dump = false;
        }
      }
    }
  }
  if (need_dump)
  {
    sprintf(tx, "file -d %d", line_at); // remove the line
    do_cmd(tx);
    sprintf(tx, "wifi %s %s", ssid.c_str(), password.c_str());
    file_append(tx);
  }
  var ok = test_config();
  if (!ok)
  {
    outs("no config file, register now");
    int code = register_this_dev();
    if (code < 0)
    {
      out_("regist failed:");
      outs(code);
    }
  }
  if (!had_timer(mqtt_loop))
    public_a_timer(mqtt_loop, Hz(11));
  if (!had_timer(rep))
    public_a_timer(rep, Second(report_time));
  change_timer_per(shine_blink, Hz(1));
  if (_in_ticking)
    resume_schedule();
  return 0;
}
int shine_blink()
{
  static int led_on = 0;
  led_on = !led_on;
  digitalWrite(LED_BUILTIN, led_on);
  return 0;
}

int rep()
{
  String jc_read_sensors();
  var data = jc_read_sensors();
  if (data == "{}")
    return -1;
  if (_rep_data_hook)
  {
    _rep_data_hook(data);
  }
  sprintf(tx, "{\"data\":%s}", data.c_str());
  String top = "control/upload/" + client_id;
  xouts(tx);
  if (!WiFi.isConnected() || !mqtt_client.connected())
  {
    var stop = stop_schedule();
    var c = sensor_data_dump(data.c_str());
    if (c < 0)
    {
      xout_("dump failed rtc=");
      xouts(c);
    }
    if (stop)
      resume_schedule();
    return -1;
  }
  // in this case we can upload data to cloudy
  var ok = mqtt_client.publish(top.c_str(), tx);
  if (!ok)
  {
    xouts("mqtt pub failed");
  }
  // upload the latest time in info
  if (sys_time_base)
  {
    var o = stop_schedule();
    var now_t = service_time();
    var code = update_local_info(now_t);
    if (code < 0)
    {
      xout_("ret=");
      xouts(code);
    }
    if (o)
      resume_schedule();
  }
  else // no time sync
  {
    start_sync_time();
  }
  return 0;
}

void on_message(char *topic, byte *payload, unsigned int length)
{
  if (!length)
    return;
  if (payload[0] == '#')
  {
    xout_('#');
    // #time:2022-02-26 10:00:00
    // #time:2022-02-26 17:40:25
    if (payload[1] == 't')
    {
      if (!sync_start_time)
      {
        xout_("unknow sys msg:");
        memcpy(tx, payload, length);
        tx[length] = '\0';
        xouts(tx);
        return;
      }
      var now = millis();
      payload += 6;
      length -= 6;
      memcpy(tx, payload, length);
      tx[length] = '\0';
      Serial.println(tx);
      // if (length > 19)
      //   return;
      datetime_t t;
      var ok = tm_from_str(&t, tx);
      if (ok < 0)
        return;
      str_tm(tx, &t);
      xout_("given:");
      xouts(tx);
      var given_time = mktime(&t);
      var dt = now - sync_start_time; // the time when server start response
      int fly_time = dt / 2000.0f;    // sec
      out_("fly:");
      out_((int)dt);
      out_(",");
      out_((int)now);
      out_(",");
      outs((int)sync_start_time);
      sys_time_base = given_time + fly_time;
      sync_end_time = now;
      sync_start_time = 0; // clear for next sync
      xouts("finish sync");
      var now_t = service_time();

      var stop = stop_schedule();
      var code = update_local_info(now_t);
      if (code < 0)
      {
        xouts("update local info failed");
        xout_("ret=");
        xouts(code);
      }
      if (stop)
        resume_schedule();

      str_tm(tx, now_t);
      xouts(tx);
      return;
    }
    return;
  }
  for (unsigned int i = 0; i < length; i++)
  {
    cmd_in((char)(payload[i])); // 打印主题内容
  }
  cmd_in('\n');
}

//-----cmds----

int cmd_led(int n, char *args[])
{
  if (n <= 1)
    return -1;
  var s = Arg(1);
  if (s == "-l")
  {
    pinMode(LED_BUILTIN, INPUT);
    return 0;
  }
  if (s == "-u")
  {
    pinMode(LED_BUILTIN, OUTPUT);
    return 0;
  }
  if (s == "-h")
  {
    outs("led 0|1|[s [peroid]]:control state");
    outs("led -l :lock led");
    outs("led -u :unlock led");
    return 0;
  }
  if (args[1][0] == '1')
  {
    remove_a_timer(shine_blink);
    digitalWrite(LED_BUILTIN, 0);
  }
  else if (args[1][0] == '0')
  {
    remove_a_timer(shine_blink);
    digitalWrite(LED_BUILTIN, 1);
  }
  else if (args[1][0] == 's')
  {
    if (n == 2)
    {
      if (had_timer(shine_blink))
      {
        remove_a_timer(shine_blink);
      }
      else
      {
        public_a_timer(shine_blink, Hz(1));
      }
      return 1;
    }
    float f = Arg(2).toFloat();
    if (f < 1.0f || f > 3600 * 24)
    {
      return -3;
    }
    change_timer_per(shine_blink, Second(f));
    return 2;
  }
  else
  {
    outs("led 0|1|[s [peroid]]");
  }
  return 0;
}

float parse_time_peroid(String &s)
{
  int i = 0;
  int from = 0;
  var last_is_digital = false;
  var time = 0.0f;

  for (; s[i] != '\0'; i++)
  {
    var c = s[i];
    if (isD(c) || c == '.')
    {
      last_is_digital = true;
      continue;
    }
    if (!last_is_digital)
      return 0;
    var sub = s.substring(from, i);
    if (!sub || !str_is_numeric(sub.c_str()))
    {
      return 0;
    }
    var num = sub.toFloat();
    switch (c)
    {
    case 'h':
      num *= 3600.0f;
      break;
    case 'm':
      num *= 60.0f;
      break;
    case 's':
      break;

    default:
      return 0;
    }
    time += num;
    last_is_digital = false;
    from = i + 1;
  }
  if (!last_is_digital)
    return time;
  var sub = s.substring(from);
  if (!sub || !str_is_numeric(sub.c_str()))
  {
    return 0;
  }
  var num = sub.toFloat();
  time += num;
  return time;
}

int cmd_rep(int n, char *args[])
{
  if (n > 1)
  {
    var s = Arg(1);
    if (s == "-h")
    {
      outs("rep [peroid]");
      return 0;
    }

    // parse time like 12h36m12s
    // report_time = Arg(1).toFloat();
    String time_arg(args[1]);
    report_time = parse_time_peroid(time_arg);
    if (report_time < 1.0f)
    {
      outs("bad timer(-1)");
      return -1;
    }

    if (!had_timer(rep))
    {
      public_a_timer(rep, Second(report_time));
    }
    else
      change_timer_per(rep, Second(report_time));
    return 1;
  }
  rep();
  return 0;
}

int cmd_test_ad(int n, char *args[])
{
  if (n < 2)
    return -1;

  n = Arg(1).toInt();
  if (n < 1)
    return -2;
  for (int i = 0; i < n; i++)
  {
    int adc = analogRead(A0);
    outs(adc);
  }
  return 0;
}

int enter_sleep()
{
  ESP.deepSleep(report_time * 1e6);
  return 0;
}

int timer_sleep()
{
  digitalWrite(LED_BUILTIN, 0);
  rep();
  mqtt_loop();
  // mqtt_client.disconnect();
  mqtt_loop();
  outs("slp after 1s");
  add_timer_once(enter_sleep, Second(1));
  return 0;
}

int cmd_sleep(int n, char *args[])
{
  if (n == 1)
  {
    if (had_timer(timer_sleep))
    {
      remove_a_timer(timer_sleep);
      outs("sleep mode stoped");
    }
    else
    {
      outs("sleep after 5s");
      public_a_timer(timer_sleep, Second(5));
    }
    return 0;
  }
  if (n > 1)
  {
    var s = Arg(1);
    if (s == "-h")
    {
      outs("slp : toggle sleep mode");
      outs("slp after_seconds: enter sleep mode");
      return 0;
    }

    var t = Arg(1).toFloat();
    if (t < 1)
    {
      remove_a_timer(timer_sleep);
      outs("sleep mode stoped");
      return 4;
    }
    if (!had_timer(timer_sleep))
    {
      public_a_timer(timer_sleep, Second(t));
    }
    else
    {
      change_timer_per(timer_sleep, Second(t));
    }
  }

  return 0;
}

// int times_setup(int n, char *args[])
// {
//   struct tm given_t;
//   int i = 1;
//   given_t.tm_year = Arg(i++).toInt();
//   given_t.tm_mon = Arg(i++).toInt();
//   given_t.tm_mday = Arg(i++).toInt();
//   given_t.tm_hour = Arg(i++).toInt();
//   given_t.tm_min = Arg(i++).toInt();
//   given_t.tm_sec = Arg(i++).toInt();
//   time_t t = mktime(&given_t);
//   xout_(t);
//   xouts(" sec");
//   var pass_sec = 3600 * 12;
//   t += pass_sec;
//   var s = ctime(&t);
//   xouts(s);
//   var now_tm = gmtime(&t);
//   xout_(now_tm->tm_year);
//   xout_('-');
//   xout_(now_tm->tm_mon);
//   xout_('-');
//   xout_(now_tm->tm_mday);
//   xout_(' ');
//   xout_(now_tm->tm_hour);
//   xout_(':');
//   xout_(now_tm->tm_min);
//   xout_(':');
//   xout_(now_tm->tm_sec);
//   xout_('\n');
//   return 0;
// }

int beep_pin = -1;
bool beep_pin_is_digital = True;
int beep_pin_volume = 512;

int beep_status = -1;

int timer_for_beep()
{
  switch (beep_status)
  {
  case 0:
    if (beep_pin_is_digital)
      digitalWrite(beep_pin, 0);
    else
      analogWrite(beep_pin, 0);

    // digitalWrite(beep_pin, 0); // sleep
    add_timer_once(timer_for_beep, Second(0.2));
    beep_status = 1;
    break;
  case 1:
    if (beep_pin_is_digital)
      digitalWrite(beep_pin, 1);
    else
      analogWrite(beep_pin, beep_pin_volume);

    // digitalWrite(beep_pin, 1); // pulse up
    add_timer_once(timer_for_beep, Second(0.1));
    beep_status = 2;
    break;
  case 2:
    if (beep_pin_is_digital)
      digitalWrite(beep_pin, 0);
    else
      analogWrite(beep_pin, 0);

    // digitalWrite(beep_pin, 0); // end
    beep_status = -1;
    break;
  default:
    if (beep_pin_is_digital)
      digitalWrite(beep_pin, 0);
    else
      analogWrite(beep_pin, 0);

    // digitalWrite(beep_pin, 0);
    break;
  }
  return 0;
}

int _short_beep()
{
  if (beep_pin < 0)
    return -1;
  if (beep_status >= 0) // not finished
    return -2;
  if (beep_pin_is_digital)
    digitalWrite(beep_pin, 1);
  else
    analogWrite(beep_pin, beep_pin_volume);
  beep_status = 2;
  add_timer_once(timer_for_beep, Second(0.1));
  return 0;
}
int beep()
{
  if (beep_pin < 0)
    return -1;
  if (beep_status >= 0 && beep_status != 3) // not finished
    return -2;
  // digitalWrite(beep_pin, 1); // start
  if (beep_pin_is_digital)
    digitalWrite(beep_pin, 1);
  else
    analogWrite(beep_pin, beep_pin_volume);

  beep_status = 0;
  add_timer_once(timer_for_beep, Second(0.1));
  return 0;
}

int cmd_beep(int n, char *args[])
{
  if (n < 2)
  {
    beep();
    return 0;
  }
  var mode = Arg(1);
  if (mode == "-sa")
  {
    if (n < 3)
      return -__LINE__;
    String v2(args[2]);
    var p = cast_to_pin(v2);
    if (p < 0)
    {
      return p;
    }
    if (beep_pin >= 0) // reset the original pin
    {
      if (beep_pin_is_digital)
        pinMode(beep_pin, INPUT);
      else
        analogWrite(beep_pin, 0);
    }
    out_("set beep PWM-pin on:");
    outs(v2);
    beep_pin = p;
    beep_pin_is_digital = false;
    analogWriteFreq(500);
    analogWriteRange(1024);
    return 0;
  }

  if (mode == "-s")
  {
    if (n < 3)
      return -__LINE__;
    String v2(args[2]);
    var p = cast_to_pin(v2);
    if (p < 0)
    {
      return p;
    }
    if (beep_pin >= 0) // reset the original pin
    {
      if (beep_pin_is_digital)
        pinMode(beep_pin, INPUT);
      else
        analogWrite(beep_pin, 0);
    }
    out_("set beep pin on:");
    outs(v2);
    pinMode(p, OUTPUT);
    beep_pin = p;
    beep_pin_is_digital = true;
    digitalWrite(p, 0);
    return 0;
  }
  if (mode == "-p") // pulse
  {
    var ok = beep();
    if (ok < 0)
      return -__LINE__;
    float t = 1.0f;
    if (n > 2)
    {
      t = Arg(2).toFloat();
      if (t < 1.0f)
      {
        return -2;
      }
    }
    create_or_change_quick_timer_count(beep, Second(t));
    return 0;
  }
  if (mode == "-t") // timer
  {
    if (beep_status >= 0)
      return -__LINE__;
    if (beep_pin < 0)
      return -__LINE__;
    if (n == 2)
    {
      if (beep_pin_is_digital)
        digitalWrite(beep_pin, 1);
      else
        analogWrite(beep_pin, beep_pin_volume);
      beep_status = 3;
      return 0;
    }
    float t;
    t = Arg(2).toFloat();
    if (t < 1.0f)
    {
      return -__LINE__;
    }

    if (beep_pin_is_digital)
      digitalWrite(beep_pin, 1);
    else
      analogWrite(beep_pin, beep_pin_volume);
    create_or_change_quick_timer_count(timer_for_beep, Second(t));
    return 0;
  }

  if (mode == "-pf")
  {
    if (n < 3)
      return -__LINE__;
    String v2(args[2]);
    int p = v2.toInt();
    if (p <= 0 || p > 1000)
    {
      return -__LINE__;
    }
    analogWriteFreq(p);
    return 0;
  }
  if (mode == "-pv")
  {
    if (n < 3)
      return -__LINE__;
    String v2(args[2]);
    int p = v2.toInt();
    if (p <= 0 || p >= 1024)
    {
      return -__LINE__;
    }
    beep_pin_volume = p;
    return 0;
  }
  outs("beep :start beep");
  outs("beep -s pin : setup pin for beep");
  outs("beep -sa pin : setup PWM pin for beep");
  outs("beep -p [peroid] : beep a pulse");
  outs("beep -pf fre : change PWM frequency");
  outs("beep -pv vol : change PWM duty cycle");
  outs("beep -t [peroid] : continuing beep sound");
  return 0;
}

void *__lily_out_adapter(void *arg, char *msg, int len)
{
  out_(msg);
  return NULL;
}
